Skip to content

Move Where onto Periscope; retire LogKit + LogViewerUI#94

Open
kyleve wants to merge 12 commits into
mainfrom
cursor/migrate-where-to-periscope
Open

Move Where onto Periscope; retire LogKit + LogViewerUI#94
kyleve wants to merge 12 commits into
mainfrom
cursor/migrate-where-to-periscope

Conversation

@kyleve

@kyleve kyleve commented Jul 16, 2026

Copy link
Copy Markdown
Owner

What & why

Migrates the Where app and every one of its modules from the old LogKit / LogViewerUI logging stack onto Periscope, then removes LogKit and LogViewerUI entirely. This is the full structured redesign (plan option B): every logging call site is now a typed LogEvent on a Log<Event> scope tree, a single process-global PeriscopeStore replaces the two old LogStore ring buffers, and the DEBUG developer surface adopts the PeriscopeTools UI.

Landed as one commit per plan step, keeping the tree green at each step (LogKit survived until its last consumer moved over).

Logging design

  • Scope tree — a WhereLog facade vends a "Where" root Log with grouping scopes (location, reminders, backup, widgets, session, evidence, recentActivity); each collaborator derives its own typed LogEvent leaf (WhereLog.<group>(SomeLog.self) / WhereLog.root(SomeLog.self)). RegionKit keeps its own "RegionKit" root scope but emits into the same Periscope.shared.
  • Typed events — one LogEvent enum per collaborator, carrying the fields the old messages interpolated; catch-path events attach LogAttachment.error(_:). Severity conventions (info/warning/error/fault) and the PII-free .public contract are preserved; hot paths stay quiet.
  • URL externalIDs — every object event stamps its externalID with the object's canonical URL identity, so the log tooling's inspect-by-object shares the same key the store/backups use — a namespaced, collision-free replacement for ad-hoc bare strings. WhereCore/WhereUI events use store:// (DataIssueID.storeURL for dismissals; a new WhereStoreID vends store://days/…, store://years/…, store://evidence/…, store://samples/…). RegionKit can't see the app's store:// types, so it owns a parallel region:// scheme (RegionURL + Region.regionURLregion://regions/<id>, modeled on StoreURL) and RegionAttributorLog keys on that — a separate namespace, since regions are a bundled catalog, not store rows.
  • One store — a single on-disk PeriscopeStore is attached to Periscope.shared at launch (AppDelegateWhereLaunch.bootstrapLogging), with retention pruning, exposed to the UI via an optional WhereModel.logStore. Durable logging is intentionally active in release and on background/headless launches too; widgets / share extension / intents run in their own processes and stay OSLog-only.
  • Developer UI (DEBUG)PeriscopeViewer replaces the old two-buffer LogViewer; adds an OpenSpansView, a Log View Mode toggle bound to a PeriscopeInspector, .logInspectable(_:) on high-value rows, .logContext(_:) at the root, and a PeriscopeAlerter (threshold .warning) wired to an in-app toast handler.

Build & module wiring

  • Package.swift / Project.swift: dropped the LogKit + LogViewerUI products, targets, test bundles, and schemes; swapped their deps for PeriscopeCore (RegionKit, WhereCore, WhereIntents, WhereWidgets, WhereShareExtension) and PeriscopeCore/PeriscopeUI/PeriscopeTools on WhereUI; unlisted them from the Where app, RegionViewer, and the Stuff-iOS-Tests scheme.
  • Deleted Shared/LogKit/ and Shared/LogViewerUI/.
  • Double-link caution respected: WhereUI (dynamic framework) statically embeds the Periscope products, so WhereUITests / WhereIntentsTests get them transitively and keep them out of extraPackageProducts.

Docs & dogfooding

  • Updated the root + Where/module AGENTS.md / README.md logging and dependency sections; regenerated the gitignored CLAUDE.md mirrors via ./sync-agents.
  • Appended the rough edges hit while dogfooding the API to Shared/Periscope/TODOs.md: late-attached sinks don't backfill the pre-attach recent buffer; inspect-by-object is scope- not instance-granular; and there's no eager (non-async) store handle. (The LogContextProviding parent-hierarchy, multi-process/App Group store sharing, and PeriscopeTools-on-Broadway gaps were already tracked.)

Testing

  • ./swiftformat --lint — clean.
  • tuist test Stuff-iOS-Tests (the full multi-bundle scheme, where the double-link regression reproduces) — all green.
  • Rewrote WhereLogTests against the new scope tree / event payloads, added WhereStoreIDTests and RegionURLTests pinning the exact identity strings, and updated ScreenHostingTests (LogViewerPeriscopeViewer, inspector wiring). The double-link regression guard WhereStylesheetTests.resolvesTraitAwareTokensFromTheBroadwayRoot passes.

Notes

Review notes

A code-review pass raised five items; outcomes:

  1. Durable on-disk logging in release + on background wakes — intended, no change.
  2. Untested bootstrapLogging failure path / retention prune — left as-is (the prune mechanism is covered at the Periscope layer; the remainder is process-global glue not worth an injection seam).
  3. externalID test coverage — done as part of the store:// canonicalization above (WhereStoreIDTests + updated WhereLogTests).
  4. DEBUG toast alerter threshold .warningkept as-is.
  5. configureDeveloperLogging rebuild guard (latent, safe today) — kept as-is.

kyleve added 12 commits July 15, 2026 18:48
Rebuild RegionLog as a Periscope facade: one "RegionKit" root scope with a
typed LogEvent per collaborator (RegionAttributor / RegionCatalog /
RegionGeometryCatalog), emitting into the process-wide Periscope.shared system
instead of a private LogKit LogStore. Errors ride as LogAttachments and region
ids as externalIDs. Swap RegionKit's LogKit dependency for PeriscopeCore and
add PeriscopeCore/UI/Tools to WhereUI (RegionMapView's one RegionLog call site
moves over now to keep the tree green). Closes plan step: Migrate RegionKit.
Rebuild WhereLog as a Periscope facade rooted at a "Where" scope with grouping
scopes (location, reminders, backup, widgets, session, evidence,
recentActivity) and derive a typed LogEvent per collaborator. Convert every
WhereCore call site to structured events, add measure() spans for store open,
backup archive read/write, and recent-activity generation, attach errors via
LogAttachment.error, and stamp day/sample/evidence externalIDs. Drop the dead
FoundationModelSummaryGenerator logger and the unused appDelegate category.

The legacy LogKit-backed WhereLog API (subsystem/store/Category/channel) is kept
alongside the new tree so WhereUI and the extensions stay green until their own
migration commits; WhereCore gains a PeriscopeCore dependency. Closes plan step:
Migrate WhereCore.
Convert every WhereUI view-model/view logging site to typed Periscope
LogEvents under the right scope: WhereModel/WhereLaunch/LoggedDaysModel as
"Where" leaves; WhereSession/YearReport/Backup/Resolve/CalendarView under the
session group; the evidence models under the evidence group; RecentActivityModel
under recentActivity. Add a per-collaborator LogEvent file for each, drop the
unused RemindersSettingsModel logger, and point DeveloperToolsView's legacy
LogViewer at the single WhereLog buffer (RegionKit no longer feeds a
LogViewerUI store) pending the PeriscopeTools rework. Closes plan step: Migrate
WhereUI view models.
Wire the Where app's durable logging at process launch: open one on-disk
PeriscopeStore, attach it to Periscope.shared as the durable sink, start
the built-in ambient sources, and prune history past a two-week retention
window. Runs off the launch critical path (the OSLog sink covers the
pre-attach window), and hands the opened store to WhereModel so the DEBUG
developer surface can browse persisted history.

Closes the store-bootstrap plan step.
Rework the Where app's developer tools onto Periscope's UI:

- DeveloperToolsView pushes PeriscopeViewer over the process-global store
  and adds an OpenSpansView monitor, replacing the LogViewerUI viewer.
- RootView builds a PeriscopeInspector once the launch bootstrap opens the
  store, injects it via .periscopeInspector, and adds a "Log View Mode"
  toggle bound to it; debugLogInspectable(_:) badges are sprinkled on the
  evidence rows and the location status row (no-ops in release).
- A DEBUG PeriscopeAlerter (threshold .warning) feeds an in-app toast
  surface (DeveloperToastCenter/Overlay) mounted above the app.
- RootView seeds the root \.logContext so view-level freeform logging lands
  under the "Where" scope.
- New DEBUG-only Strings for the open-spans and Log View Mode rows; hosting
  + Strings tests updated.

Closes the dev-ui plan step.
Convert the WhereIntents, WhereShareExtension, and WhereWidgets logging
call sites from the legacy LogKit facade (WhereLog.channel(_:)) to typed
Periscope LogEvents emitted on WhereLog.root(_:) leaf scopes. Each
extension runs in its own process, so Periscope.shared stays OSLog-only
(no persistent store).

Adds one LogEvent enum per extension (WhereIntentsLog, ShareExtensionLog,
WhereWidgetsLog) carrying the fields the old messages interpolated, with
error attachments on catch paths. Swaps the LogKit dependency for
PeriscopeCore on WhereIntents (Package.swift) and WhereWidgets /
WhereShareExtension (Project.swift).

Closes the extensions plan step.
Every consumer now emits through Periscope, so retire the legacy logging
stack entirely. Drops the LogKit-backed facade from WhereLog (the
subsystem/store/Category/channel API and its tests), removes the LogKit /
LogViewerUI products, targets, test bundles, and schemes from
Package.swift and Project.swift, unlists them from the Where app and
RegionViewer deps and the Stuff-iOS-Tests scheme, and deletes
Shared/LogKit and Shared/LogViewerUI.

Verified: project regenerates, ./swiftformat --lint clean, WhereCoreTests
green, and the full Stuff-iOS-Tests scheme builds and links.

Closes the remove-logkit plan step.
Rewrites the Where feature logging section (WhereLog as a Periscope scope
tree, typed LogEvent leaves, one PeriscopeStore sink, OSLog-only
extensions), swaps the LogKit dependency mentions for PeriscopeCore in the
extension + RegionViewer + WhereIntents docs, and refreshes the root
double-link note and never-swallow-errors rule for Periscope.

Appends the rough edges hit while dogfooding the API: late-attached
sinks don't backfill the pre-attach recent buffer, inspect-by-object is
scope- not instance-granular, and there's no eager (non-async) store
handle. Regenerates the gitignored CLAUDE.md mirrors via ./sync-agents.

Closes the docs-dogfood plan step.
Records the post-#94/#96 follow-up to replace ad-hoc bare-string
Periscope LogEvent externalIDs with the app's store:// object identities
(one namespaced key across DB rows, backups, and logs). Notes the seams:
move StoreURL to a module RegionKit can see, define store:// identities
for the remaining object families, and pick synthetic identities for
year/scope correlations. Deferred out of #94 to avoid coupling two open
PRs.
…to-periscope

# Conflicts:
#	Where/WhereUI/Sources/Resolution/ResolveModel.swift
Now that the store:// scheme (#96) is on main, key every store-object
LogEvent externalID on the object's canonical store:// identity so the log
tooling's inspect-by-object shares the same key the store and backups use
— a namespaced, collision-free replacement for the ad-hoc bare strings
(a year "2025" and an evidence UUID can no longer clash in one flat index).

- Adds WhereStoreID (WhereCore, beside StoreURL): store://days/<iso>,
  store://years/<n>, store://evidence/<uuid>, store://samples/<uuid>,
  built via StoreURL, mirroring how DataIssueID vends store://issues/….
- Points the WhereCore (DayJournal, WidgetSnapshotPublisher,
  LocationIngestor, BackupService) and WhereUI (AddEvidence, EvidenceDetail,
  EvidenceList, LoggedDays, YearReport) event externalIDs at it; dismissals
  already ride DataIssueID.storeURL after the main merge.
- RegionKit stays the deliberate exception: it's below the app's store://
  convention and its regions are a bundled catalog, not store rows, so
  RegionAttributorLog keeps its bare catalog-id externalID.
- Adds WhereStoreIDTests pinning the exact URL strings + StoreURL
  round-trip; updates WhereLogTests' externalID assertions; refreshes the
  Where/WhereCore AGENTS logging notes and drops the now-done TODO.

Verified: ./swiftformat --lint clean and the full Stuff-iOS-Tests scheme green.
Adds RegionURL, RegionKit's local analog of WhereCore's StoreURL: a
builder/parser for region://<collection>/<type>?<params> URLs, so a
RegionKit value can vend a stable, namespaced URL identity without
reaching up into app code for the store:// scheme. Region gains a
regionURL (region://regions/<id>) mirroring how DataIssueID vends
storeURL, distinct from Region's bare-rawValue storage Codable.

RegionAttributorLog now carries a typed Region and keys its externalID on
region.regionURL.absoluteString, so inspect-by-object works for regions
too — a separate, intentionally parallel namespace to store:// (regions
are a bundled catalog, not store rows).

Adds RegionURLTests (builder round-trip, foreign-scheme rejection, and the
Region identity), updates RegionLogTests for the typed payload, and
refreshes the Where / RegionKit AGENTS logging notes.

Verified: ./swiftformat --lint clean and the full Stuff-iOS-Tests scheme green.
@kyleve kyleve marked this pull request as ready for review July 16, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant